package com.product.config.listener;


import cn.dev33.satoken.stp.StpUtil;
import com.common.EquityStatus;
import com.common.Message.vo.statusMessage;
import com.common.ResultCode;
import com.common.RocketMqConstant;
import com.common.other.domain.entity.SysLog;
import com.product.config.Exception.ServiceExceptionUtil;
import com.product.dao.ProductEquityDao;
import com.product.entity.ProductEquity;
import com.product.rocketmq.RocketMqTemplate;
import com.product.util.SPUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.sql.Date;
import java.text.DateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @program: Member_Perks_Store
 * @description: 状态机监听器
 * @author: 无恙
 * @create: 2024-06-19 18:59
 **/


@Intercepts({
        //type指定代理的是那个对象，method指定代理Executor中的那个方法,args指定Executor中的query方法都有哪些参数对象
        //由于Executor中有两个query，因此需要两个@Signature
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),//需要代理的对象和方法
})
@Component
public class UpdateInterceptor implements Interceptor {
    private static final String TABLE_NAME = "product_equity";

    private static final String STATE_FIELD = "product_state";
    //UPDATE product_equity SET product_state=4 WHERE (id = 1)，匹配product_state=4,
    private static final String STATE_PATTERN = "product_state\\s*=\\s*(\\d+).*WHERE\\s*\\(id\\s*=\\s*(\\d+)\\)";


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            //获得查询方法的参数，比如selectById(Integer id,String name)，那么就可以获取到四个参数分别是：
            //{id:1,name:"user1",param1:1,param2:"user1"}
            parameter = invocation.getArgs()[1];
        }

        //将参数和映射文件组合在一起得到BoundSql对象
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);

        //获取配置信息
        Configuration configuration = mappedStatement.getConfiguration();


        //通过配置信息和BoundSql对象来生成带值得sql语句
        String sql = geneSql(configuration, boundSql);
        //解析SQL语句，判断表名和状态字段
        if (sql.contains("UPDATE") && sql.contains(TABLE_NAME) && sql.contains(STATE_FIELD)) {
            //获取product_state的值,正则表达则匹配
            Matcher matcher = Pattern.compile(STATE_PATTERN).matcher(sql);
            System.err.println(sql);
            if(matcher.find()){
                String state = matcher.group(1);
                String id = matcher.group(2);
                //手动获取bean
                ProductEquityDao bean = SPUtil.getBean(ProductEquityDao.class);
                ProductEquity productEquity = bean.selectById(id);
                //状态机判断
                if(!EquityStatus.canTransition(
                        EquityStatus.fromCode(productEquity.getProductState()),
                        EquityStatus.fromCode(Integer.parseInt(state)))
                ){
                   throw ServiceExceptionUtil.exception(ResultCode.STATE_CHANGE_EXCEPTION);

                }
                statusMessage statusMessage = new statusMessage();
                statusMessage.setSysLog(new SysLog(null,state, (Long) StpUtil.getLoginId(),new java.util.Date()));
                SPUtil.getBean(RocketMqTemplate.class).send(RocketMqConstant.PRODUCT_TOPIC, statusMessage);

            }

        }
        // 确保只调用一次 proceed
        try {

            return invocation.proceed();
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            throw e;
        }



    }

    /**
     * 如果是字符串对象则加上单引号返回，如果是日期则也需要转换成字符串形式，如果是其他则直接转换成字符串返回。
     * @param obj
     * @return
     */
    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    /**
     * 生成对应的带有值得sql语句
     * @param configuration
     * @param boundSql
     * @return
     */
    public static String geneSql(Configuration configuration, BoundSql boundSql) {

        Object parameterObject = boundSql.getParameterObject();//获得参数对象，如{id:1,name:"user1",param1:1,param2:"user1"}
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获得映射的对象参数
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");//获得带问号的sql语句
        if (parameterMappings.size() > 0 && parameterObject != null) {//如果参数个数大于0且参数对象不为空，说明该sql语句是带有条件的
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//检查该参数是否是一个参数
                //getParameterValue用于返回是否带有单引号的字符串，如果是字符串则加上单引号
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));//如果是一个参数则只替换一次，将问号直接替换成值

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);//将映射文件的参数和对应的值返回，比如：id，name以及对应的值。
                for (ParameterMapping parameterMapping : parameterMappings) {//遍历参数，如:id,name等
                    String propertyName = parameterMapping.getProperty();//获得属性名，如id,name等字符串
                    if (metaObject.hasGetter(propertyName)) {//检查该属性是否在metaObject中
                        Object obj = metaObject.getValue(propertyName);//如果在metaObject中，那么直接获取对应的值
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));//然后将问号?替换成对应的值。
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;//最后将sql语句返回
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //properties可以通过
        System.out.println(properties);
    }


}
